//
// Notes
//  - The divs in each cell are for 2 reasons:
//    To keep the bordering alive and to use for slideDown
//


// Retrieved from:
// http://spudly.shuoink.com/2008/05/31/arrayintersection/
// Complements to Stephen Sorensen
Array.prototype.intersection = function( setB ) {  
   var setA = this;  
  
   var setA_seen = {};  
   var setB_seen = {};  
   for ( var i = 0; i < setB.length; i++ ) {  
      setB_seen[ setB[i] ] = true;  
   }  
  
   var intersection = [];  
   for ( var i = 0; i < setA.length; i++ ) {  
      if ( !setA_seen[ setA[i] ] ) {  
         setA_seen[ setA[i] ] = true;  
         if ( setB_seen[ setA[i] ] ) {  
            intersection.push( setA[i] );  
         }  
      }  
   }  
   return intersection;  
}


function TimeNGears() {
    this.is_saved = true; // start off saved so can open without annoying dialog
    this.days_of_week = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'];

    // notification
    this.notification_fadein_delay = 150;
    this.notification_display_delay = 1000;
    this.notification_fadeout_delay = 2000;

    this.db_fields = {'trac_number':'tracCell','description':'jobCell','monday':'monday','tuesday':'tuesday','wednesday':'wednesday','thursday':'thursday','friday':'friday','saturday':'saturday','sunday':'sunday'};

    this.window = new Window();
}

TimeNGears.prototype.load = function() {

    // check if gears is installed
    this.checkGearsInstallation();

    // initialise the database if necessary
    this.initDb();

    // decorate the html with functionality
    this.decorate();
}

TimeNGears.prototype.checkGearsInstallation = function() {
    if (!window.google || !google.gears) {
        location.href = "http://gears.google.com/?action=install&message=To run the timesheet please install Google Gears" +
                        "&return=x";
    }
}

TimeNGears.prototype.initDb = function() {

    // Open this page's local database.
    var success = false;
 
    if (window.google && google.gears) {
        try {
            this.db = google.gears.factory.create('beta.database');
 
            if (this.db) {
                this.db.open('timesheet');
                this.db.execute('create table if not exists timesheet' +
                           ' (timesheet_id integer primary key autoincrement, name varchar(255) unique)');
                this.db.execute('create table if not exists entry' +
                           ' (entry_id integer primary key autoincrement, timesheet_id integer, trac_number varchar(255), description varchar(255), monday_date date, monday real, tuesday real, wednesday real, thursday real, friday real, saturday real, sunday real)');
 
                success = true;
            }
 
        } catch (ex) {
            alert('Could not create database: ' + ex.message);
        }
    }

    // Enable or disable UI elements
 
    //var inputs = document.forms[0].elements;
    //for (var i = 0, el; el = inputs[i]; i++) {
        //el.disabled = !success;
    //}
}

TimeNGears.prototype.decorate = function() {

    var that = this;

    // attach the cell editing functions
    $('td.inputCell, #timesheet_name_header').click(function(){
        // if has nbsp then text returns a space
        var value = $(this).find('span.value').hide().text();
        if (value.charCodeAt(0) == 160) {
            value = '';
        }
        $(this).find('input').show().focus().val(value).select();
    });

    $('#timesheet_name_header input').blur(function(){

        var span = $(this).parents('h2').eq(0).find('span.value');
        var value = $(this).val();
        var span_val = span.text();

        // unicode 160 is nbsp, not a ' '
        if (value !== '' && value !== span_val || value === '' && span_val.charCodeAt(0) !== 160) {
            that.setSaved(false);
        }

        if (this.value === '') {
            alert('Please enter a name');
            var value = $(this).parents('h2').eq(0).find('span.value').text();
            $(this).focus().val(value).select();
        } else {
            $(this).hide();
            $(this).parents('h2').eq(0).find('span.value').show().text(this.value);
        }
    });

    $('td.inputCell input').blur(function(){

        var span = $(this).parents('td').eq(0).find('span.value');
        var value = $(this).val();
        var span_val = span.text();

        // unicode 160 is nbsp, not a ' '
        if (value !== '' && value !== span_val || value === '' && span_val.charCodeAt(0) !== 160) {
            that.setSaved(false);
            $(this).parents('tr').attr('dirty', 1);
        }


        // hide the input
        $(this).hide();

        // if this is a template row, clone if entered something
        var parentTr = $(this).parents('tr').eq(0);
        if ($(this).val() !== '' && parentTr.hasClass('template')) {
            parentTr.clone(true).insertAfter(parentTr);  // create a new template row
            parentTr.removeClass('template').addClass('timesheetRow').find('input.delete').show();  // this is no longer the template row, and show the delete button
            that.window.realign();
            var creating_new = true;
        } else {
            var creating_new = false;
        }

        if (this.value === '') {
            $(this).parents('td').eq(0).find('span.value').show().html('&nbsp;');
        } else {
            $(this).parents('td').eq(0).find('span.value').show().text(this.value);
        }

        // total the hours for this row
        if ($(this).parents('td').eq(0).hasClass('timeCell') && !parentTr.hasClass('template') || creating_new) {
            that.runCalculation($(this).parents('td').eq(0));
        }
    });

    // capture some useful keys
    $('td.inputCell input, #timesheet_name_header input').keyup(function(event){
        var keycode = event.keyCode || event.which;

        // use escape to revert
        if (keycode == 27) {
            this.value = $(this).parents('td').eq(0).find('span.value').text();
            $(this).blur();
            return false;
        }

        // tie return key to blur
        if (keycode == 13) {
            $(this).blur();
            return false;
        }

    }).
    // use the tab key to move across a cell - only with keydown event
    keydown(function(event){

        var keycode = event.keyCode || event.which;
        if (keycode == 9) {

            $(this).blur();

            if ($(this).parents('tr.timesheetRow').length !== 0) {
                var day = that.whichDay($(this).parents('td').eq(0).attr('class'));
                var next_day = that.nextDay(day);
                $(this).parents('tr.timesheetRow').eq(0).find('td.'+next_day).click();
            }

            // prevent the default by returning false
            return false;
        }
    });


    $('button#new').click(function(){that.newTimesheet()});
    $('button#open').click(function(){that.open()});
    $('button#save').click(function(){that.save()});
    $('button#discard').click(function(){that.discard()});
    $('button#addWeek').click(function(){that.addWeek()});
    $('input.delete').click(function(){that.deleteRow(this)});
    $('button#scrollDown').click(function(){that.window.scrollDown()});
    $('button#scrollUp').click(function(){that.window.scrollUp()});
}

TimeNGears.prototype.newTimesheet = function() {

    if (!this.is_saved && !confirm('The current changes will be lost.\nAre you sure?')) {
        return;
    }
    window.location = window.location;
}

TimeNGears.prototype.open = function() {

    // confirm with the user
    if (!this.is_saved && !confirm('The current changes will be lost.\nAre you sure?')) {
        return;
    }

    // setup a dialog to ask the user which timesheet
    var rs = this.db.execute('select timesheet_id, name from timesheet');
    var select = $('<select>').width(200).attr({'id': 'open_timesheet_id', 'multiple': true, 'size': 10});
    while (rs.isValidRow()) {
        var option = $('<option>');
        option.val(rs.fieldByName('timesheet_id'));
        option.text(rs.fieldByName('name'));
        select.append(option);
        rs.next();
    }

    var that = this;
    var dialog = new YAHOO.widget.Dialog("openTimesheet", {
        fixedcenter: true,
        visible: true,
        modal: true,
        close: true,
        constraintoviewport: true,
        buttons: [ { text: "OK", handler: function() {
            that.loadTimesheet($('div#dialogContainer select#open_timesheet_id option:selected').val());
            that.setSaved(true);
            this.hide(); // removes the modal mask
            $('div#dialogContainer').empty();
            }, isDefault: true }, { text: "Cancel", handler: function() {this.hide();$('div#dialogContainer').empty();} }  ]
    });
    dialog.setHeader("Open Timesheet");
    dialog.setBody($('<div />').append(select).html());
    dialog.render("dialogContainer");
}

// responsibility: save given a timesheet name?
TimeNGears.prototype.save = function() {

    var that = this;

    // create or save the timesheet
    var timesheet_id = $('#timesheet_id').val();
    var timesheet_name = $('#timesheet_name').text();

    if (timesheet_id === '') {
        // how to get the id from here? catching the resultset then examining gives "resultset is closed"
        this.db.execute('insert into timesheet values(null, ?)', [timesheet_name]);
        timesheet_id = this.db.lastInsertRowId;
        $('#timesheet_id').val(timesheet_id);
    } else {
        this.db.execute('update timesheet set name = ? where timesheet_id = ?', [timesheet_name, timesheet_id]);
    }


    // save the dirty timesheet entries
    $('tr.timesheetRow[dirty="1"]').each(function(){

        var monday_date = $(this).parents('div').eq(0).attr('id');

        var id = $(this).attr('id');
        if (id === undefined || id === '') {
            var sql = 'insert into entry values(null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
        } else {
            var sql = 'update entry set trac_number = ?, description = ?, monday_date = ?, monday = ?, tuesday = ?, wednesday = ?, thursday = ?, friday = ?, saturday = ?, sunday = ? where entry_id = ?';
        }
        var values = new Object;
        $(this).find('td.inputCell').find('span.value').each(function(){
            var parentTd = $(this).parents('td').eq(0);
            var key;
            if (parentTd.hasClass('tracCell')) {
                key = 'trac_number';
            } else if (parentTd.hasClass('jobCell')) {
                key = 'description';
            } else if (parentTd.hasClass('monday')) {
                key = 'monday';
            } else if (parentTd.hasClass('tuesday')) {
                key = 'tuesday';
            } else if (parentTd.hasClass('wednesday')) {
                key = 'wednesday';
            } else if (parentTd.hasClass('thursday')) {
                key = 'thursday';
            } else if (parentTd.hasClass('friday')) {
                key = 'friday';
            } else if (parentTd.hasClass('saturday')) {
                key = 'saturday';
            } else if (parentTd.hasClass('sunday')) {
                key = 'sunday';
            }
            values[key] = this.innerHTML === '&nbsp;' ? null : $(this).text();
        });

        if (id === undefined || id === '') {
            that.db.execute(sql, [timesheet_id, values['trac_number'], values['description'], monday_date, values['monday'], values['tuesday'], values['wednesday'], values['thursday'], values['friday'], values['saturday'], values['sunday']]);

            // feed the newly created id back into the row
            $(this).attr('id', that.db.lastInsertRowId);

        } else {
            that.db.execute(sql, [values['trac_number'], values['description'], monday_date, values['monday'], values['tuesday'], values['wednesday'], values['thursday'], values['friday'], values['saturday'], values['sunday'], id]);
        }
    });


    that.setSaved(true);
    $('tr.timesheetRow').attr('dirty', 0);
    that.showNotification('Timesheet saved');
}

TimeNGears.prototype.discard = function() {

    if (!confirm('Are you sure?')) {
        return;
    }

    // from here reload the currently opened timesheet
}

TimeNGears.prototype.addWeek = function() {

    var that = this;
    var new_date;

    var dialog = new YAHOO.widget.Dialog("chooseWeek", {
        width: "220px",
        height: "285px",
        fixedcenter: true,
        visible: true,
        modal: true,
        close: true,
        constraintoviewport: true,
        buttons: [ { text: "OK", handler: function() {
            var dates = cal.getSelectedDates();
            that.addWeekToTimesheet(dates[0]);
            this.hide(); // removes the modal mask
            $('div#dialogContainer').empty();
            }, isDefault: true }, { text: "Cancel", handler: function() {this.hide();$('div#dialogContainer').empty();} }  ]
    });
    dialog.setHeader("Add Week");
    dialog.setBody('<div id="dialogInner"></div>');
    dialog.render("dialogContainer");

    var cal = new YAHOO.widget.Calendar("cal", "dialogInner");
    cal.render();
}

TimeNGears.prototype.addWeekToTimesheet = function(new_date) {

    // determine the date of the previous monday
    var monday_date = new Date(new_date.toString());
    monday_date.setDate(monday_date.getDate() - monday_date.getDay() + 1);

    // determine the position for the new week - ordered going down by date descending
    var monday_date_formatted = monday_date.getFullYear().toString() + '-' + this.zeroPad((monday_date.getMonth()+1).toString(),2) + '-' + this.zeroPad(monday_date.getDate().toString(),2);

    // If already exists, reject
    if ($('#' + monday_date_formatted).length > 0) {
        alert('Please choose a week that hasn\'t been created yet');
        return;
    }

    // clone the week template
    var newWeek = $('div#weekTemplate').clone(true);

    // fill out the week template
    newWeek.attr('id', monday_date_formatted);
    var friday_date = new Date(new_date.toString());
    friday_date.setDate(friday_date.getDate() + (7 - friday_date.getDay()));
    newWeek.find('td.dateRangeCell').text(monday_date.getDate() + '/' + (monday_date.getMonth() + 1) + '/' + monday_date.getFullYear() + ' - ' + friday_date.getDate() + '/' + (friday_date.getMonth() + 1) + '/' + friday_date.getFullYear());
    newWeek.find('th span.date').each(function(){
        var day_of_month = monday_date.getDate();
        var month = monday_date.getMonth() + 1 ;
        this.innerHTML = day_of_month + '/' + month;
        monday_date.setDate(day_of_month + 1);
    });

    // pass to Window to insert
    this.window.addWeek(newWeek);
}

TimeNGears.prototype.deleteRow = function(btn) {
    if (confirm('Are you sure?')) {
        var parentTr = $(btn).parents('tr').eq(0);
        var id = parentTr.attr('id');
        if (id !== '') {
            this.db.execute('delete from entry where entry_id = ?', [id]);
        }
        var week = $(btn).parents('div.week').eq(0);
        parentTr.remove();
        this.totalWeek(week.attr('id'));
        this.window.realign();
    }
}

TimeNGears.prototype.whichDay = function(classes) {
    return this.days_of_week.intersection(classes.split(' '))[0];
}

TimeNGears.prototype.nextDay = function(day) {

    switch (day) {
        case 'monday': return 'tuesday';
        case 'tuesday': return 'wednesday';
        case 'wednesday': return 'thursday';
        case 'thursday': return 'friday';
        case 'friday': return 'saturday';
        case 'saturday': return 'sunday';
        case 'sunday': return 'monday';
        default: throw 'Error';
    }
}

TimeNGears.prototype.zeroPad = function(n, digits) {
    n = n.toString();
    while (n.length < digits) {
        n = '0' + n;
    }
    return n;
}

// Calculate the column totals and then the grand total given the id of a
// certain week (a date string of the monday)
TimeNGears.prototype.totalWeek = function(week) {
    for (var i in this.days_of_week) {
        // if is numeric, and not 'intersection'
        if (parseInt(i) == i) {
            this.totalColumn(week, this.days_of_week[i]);
        }
    }
    this.totalGrand(week);
}

TimeNGears.prototype.runCalculation = function(cell) {

    // calculate horizontal total
    var tr = cell.parents('tr').eq(0);
    this.totalRow(tr);

    var week = cell.parents('div.week').eq(0).attr('id');
    var day_of_week = this.whichDay(cell.attr('class'));

    // calculate vertical total
    this.totalColumn(week, day_of_week);

    // calculate grand total - running across the top
    this.totalGrand(week);
}

// find the total hours for a given row (tr)
TimeNGears.prototype.totalRow = function(row) {

    var total = 0;
    row.find('td.timeCell').filter('td.inputCell').find('span.value').each(function(){
        // chrome returns a real nbsp 160?
        if (this.innerHTML !== '' && this.innerHTML !== '&nbsp;' && this.innerHTML.charCodeAt(0) !== 160) {
            total += parseFloat(this.innerHTML);
        }
    });
    row.find('td.totalCell span.value').text(total);
}

// find the total hours for a given column (using a day classname) and week
TimeNGears.prototype.totalColumn = function(week, day) {

    var total = 0;
    $('div#'+week+' td.'+day+' span.value').each(function(){
        // chrome returns a real nbsp 160?
        if (this.innerHTML !== '' && this.innerHTML !== '&nbsp;' && this.innerHTML.charCodeAt(0) !== 160) {
            total += parseFloat(this.innerHTML);
        }
    });
    // for vertical totals, don't display a zero - columns may be blank, rows will most likely not be
    if (total === 0) {
        // doing text(' ') will not tranlsate into nbsp =/  - at least for chrome anyway
        $('div[id="'+week+'"] td.'+day).filter('td.totalCell').html('&nbsp;');
    } else {
        $('div[id="'+week+'"] td.'+day).filter('td.totalCell').text(total);
    }
}

// find the grand total by running along the column totals
TimeNGears.prototype.totalGrand = function(week) {
    var total = 0;
    $('div#'+week+' td.'+'topTotal').each(function(){
        // chrome returns a real nbsp 160?
        if (this.innerHTML !== '' && this.innerHTML !== '&nbsp;' && this.innerHTML.charCodeAt(0) !== 160) {
            total += parseFloat(this.innerHTML);
        }
    });
    $('div#'+week+' td.grandTotal').text(total);
}

TimeNGears.prototype.showNotification = function(msg) {
    var notification = $('div#notification').text(msg).fadeIn(this.notification_fadein_delay);
    window.setTimeout(function(){
        notification.fadeOut(this.notification_fadeout_delay);
    }, this.notification_display_delay);
}

TimeNGears.prototype.setSaved = function(is_saved) {
    this.is_saved = is_saved;
    if (is_saved) {
        $('#unsaved').hide();
        $('button#save').attr('disabled', 'disabled');
        $('button#discard').attr('disabled', 'disabled');
    } else {
        $('#unsaved').show();
        $('button#save').removeAttr('disabled');
        $('button#discard').removeAttr('disabled');
    }
}

TimeNGears.prototype.loadTimesheet = function(timesheet_id) {

    // find out the timesheet name
    // (maybe just pass the name from the ui?)
    var rs = this.db.execute('select name from timesheet where timesheet_id = ?', [timesheet_id]);
    var timesheet_name = rs.field(0);

    // update screen
    $('div#timesheet').empty();
    $('#timesheet_name').text(timesheet_name);
    $('#timesheet_id').val(timesheet_id);

    // load the timesheet
    var rs = this.db.execute('select count(*) from entry where timesheet_id = ?', [timesheet_id]);
    if (rs.field(0) === 0) {
        rs.close();
        var monday_date = new Date;
        monday_date.setDate(monday_date.getDate() - monday_date.getDay() + 1);
    } else {
        rs.close();
        var rs = this.db.execute('select * from entry where timesheet_id = ? order by monday_date desc, entry_id', [timesheet_id]);
        var current_week = -1;
        var weeks = 0;
//        var top_offset = 0;
        var newWeek;
        var week_template = $("#weekTemplate");
        while (rs.isValidRow()) {

            var db_monday_date = rs.fieldByName('monday_date');

            // create a new table for each week
            if (current_week != db_monday_date) {

                weeks++;
                if (weeks > this.window.length) {
                    break;
                }

//                if (newWeek !== undefined) {
//                    top_offset += newWeek.height();
//                }

                if (db_monday_date !== null) {
                    var monday_date_pieces = db_monday_date.split('-');
                    var monday_date = new Date(monday_date_pieces[0], monday_date_pieces[1] - 1, monday_date_pieces[2]);
                } else {
                    var monday_date = new Date;
                    monday_date.setDate(monday_date.getDate() - monday_date.getDay() + 1);
                }
                current_week = this.zeroPad(monday_date.getFullYear().toString(),2) + '-' + this.zeroPad((monday_date.getMonth()+1).toString(),2) + '-' + this.zeroPad(monday_date.getDate().toString(),2);

                newWeek = week_template.clone(true);
                newWeek.attr('id', current_week); //.css('top', top_offset);

                var friday_date = new Date(monday_date);
                friday_date.setDate(friday_date.getDate() + (7 - friday_date.getDay()));
                newWeek.find('td.dateRangeCell').text(monday_date.getDate() + '/' + (monday_date.getMonth() + 1) + '/' + monday_date.getFullYear() + ' - ' + friday_date.getDate() + '/' + (friday_date.getMonth() + 1) + '/' + friday_date.getFullYear());

                newWeek.find('th span.date').each(function(){
                    var day_of_month = monday_date.getDate();
                    var month = monday_date.getMonth() + 1 ;
                    this.innerHTML = day_of_month + '/' + month;
                    monday_date.setDate(day_of_month + 1);
                });

                newWeek.appendTo("#timesheet");
            }

            var template = $('#' + current_week + ' tr.template');
            var newRow = template.clone(true);
            newRow.removeClass('template').find('input.delete');
            for (var i in TimeNGears.db_fields) {
                var value = rs.fieldByName(i);
                if (value === '') {
                    newRow.find('td.'+TimeNGears.db_fields[i]+' span.value').html('&nbsp;');
                } else {
                    newRow.find('td.'+TimeNGears.db_fields[i]+' span.value').text(value);
                }
            }

            newRow.attr('id', rs.fieldByName('entry_id'));
            newRow.addClass('timesheetRow');
            newRow.insertBefore(template).show();
            this.totalRow(newRow);
            rs.next();

        }
        rs.close();

        var that = this;
        $('div.week').each(function(){
            that.totalWeek($(this).attr('id'));
        });
    }
}

// select all the timesheet entries for the given week
TimeNGears.prototype.createWeek = function(timesheet_id, monday_date_string) {

    // find where we need to put it
    var top_offset = 0;
    var last;
    $('#timesheet .week').each(function(){
        top_offset += $(this).height();
        last = this;
    });
    top_offset -= $(last).height();

    // clone the week template
    var monday_date_pieces = monday_date_string.split('-');
    var monday_date = new Date(monday_date_pieces[0], monday_date_pieces[1] - 1, monday_date_pieces[2]);
    var newWeek = $('div#weekTemplate').clone(true).appendTo('div#timesheet').show().attr('id', monday_date_string).css({'top': top_offset, 'left': 1000});

    // add the week's date range label
    var friday_date = new Date(monday_date);
    friday_date.setDate(friday_date.getDate() + (7 - friday_date.getDay()));
    newWeek.find('td.dateRangeCell').text(monday_date.getDate() + '/' + (monday_date.getMonth() + 1) + '/' + monday_date.getFullYear() + ' - ' + friday_date.getDate() + '/' + (friday_date.getMonth() + 1) + '/' + friday_date.getFullYear());

    // add the dates to the header row, using monday_date to iterate
    newWeek.find('th span.date').each(function(){

        var day_of_month = monday_date.getDate();
        var month = monday_date.getMonth() + 1 ;

        // set the label
        this.innerHTML = day_of_month + '/' + month;

        // go to the next day
        monday_date.setDate(day_of_month + 1);
    });

    var rs = this.db.execute('select * from entry where timesheet_id = ? and monday_date = ? order by entry_id', [timesheet_id, monday_date_string]);
    while (rs.isValidRow()) {

        var template = newWeek.find('tr.template');
        var newRow = template.clone(true);
        newRow.insertBefore(template).removeClass('template').find('input.delete').show();
        for (var i in TimeNGears.db_fields) {
            var value = rs.fieldByName(i);
            if (value === '') {
                newRow.find('td.'+TimeNGears.db_fields[i]+' span.value').html('&nbsp;');
            } else {
                newRow.find('td.'+TimeNGears.db_fields[i]+' span.value').text(value);
            }
        }

        newRow.attr('id', rs.fieldByName('entry_id'));
        newRow.addClass('timesheetRow');
        this.totalRow(newRow);

        rs.next();
    }
    rs.close();
}
